﻿using System;
using System.Threading;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;

namespace PedesTrainProject.Traffic
{
    [Serializable()]
    public class CarFlow : ISerializable
    {
        static private List<Car> cars;
        static private List<KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>> inFlowFlags;
        private List<Thread> inFlowFlagThreads = new List<Thread>();
        private Thread carsThread;
        private Thread animationThread;

        static private List<List<int>> destinations = new List<List<int>>();

        static private bool running, releaseRunning, paused = false;
        private Core.ICoreTraffic coreTraffic;
        private int totalCar;
        private int totalJam;

        public int TotalJam
        {
            get { return totalJam; }
        }

        public int getTotalCar()
        {
            return totalCar;
        }

        public CarFlow(List<KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>> _inFlowFlags, Core.ICoreTraffic _coreTraffic)
        {
            coreTraffic = _coreTraffic;
            inFlowFlags = _inFlowFlags;
            cars = new List<Car>();

            initialize();
        }

        public CarFlow(List<KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>> _inFlowFlags, List<Car> _cars, Core.ICoreTraffic _coreTraffic)
        {
            coreTraffic = _coreTraffic;
            inFlowFlags = _inFlowFlags;
            cars = _cars;
            
            initialize();
        }

        public void initialize()
        {
            totalCar = 0;
            totalJam = 0;

            foreach (KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>> inFlowFlag in inFlowFlags)
            {
                Grid.Grid grid = coreTraffic.getGrid();
                List<int> int_grid = new List<int>();
                for (int i = 0; i < grid.Width * grid.Height; i++) int_grid.Add(0);

                List<int> _destinations = new List<int>();
                _destinations = investigatePaths(grid, int_grid, _destinations, inFlowFlag.Value.Key, inFlowFlag.Value.Key);
                if (_destinations.Count == 0) ;//Console.WriteLine("ochujales? flaga na " + inFlowFlag.Value.Key + " jest smutna :(((");
                else
                {
                  //  Console.WriteLine("Jestem w " + inFlowFlag.Value.Key + " " + inFlowFlag.Value.Value);
                    destinations.Add(_destinations);
                    inFlowFlagThreads.Add(new Thread(releaseCars));
                    //Console.WriteLine("flag_index = ");
                    //Console.WriteLine("Dodalem flage " + inFlowFlag.Value.Key + " " + inFlowFlag.Value.Value);
                }
            }
            carsThread = new Thread(moveCars);
            animationThread = new Thread(animateCars);
        }

        public void addCar(Car car)
        {
            cars.Add(car);
            totalCar++;
        }

        public void removeCar(Car car)
        {
            cars.Remove(car);
        }

        private List<int> investigatePaths(Grid.Grid grid, List<int> int_grid, List<int> _destinations, int starter, int current)
        {
            //Console.WriteLine("current = " + current + ", destinations = " + _destinations.Count);
            int_grid[current] = 1;
            foreach (Grid.Lane lane in grid.Tiles[current].Road.Lanes)
            {
              //  Console.WriteLine("lane");
                if (lane.Exit == true)
                {
                    if (lane.Direction == Grid.Directions.North)
                    {
                        if (current - grid.Width < 0)
                        {
                            if (current != starter) _destinations.Add(current);
                        }
                        else if (int_grid[current - grid.Width] != 1) _destinations = investigatePaths(grid, int_grid, _destinations, starter, current - grid.Width);
                    }
                    else if (lane.Direction == Grid.Directions.East)
                    {
                        if ((current + 1) % grid.Width == 0)
                        {
                            if (current != starter) _destinations.Add(current);
                        }
                        else if (int_grid[current + 1] != 1) _destinations = investigatePaths(grid, int_grid, _destinations, starter, current + 1);
                    }
                    else if (lane.Direction == Grid.Directions.South)
                    {
                        if (current + grid.Width >= grid.Tiles.Count)
                        {
                            if (current != starter) _destinations.Add(current);
                        }
                        else if (int_grid[current + grid.Width] != 1) _destinations = investigatePaths(grid, int_grid, _destinations, starter, current + grid.Width);
                    }
                    else if (lane.Direction == Grid.Directions.West)
                    {
                        if (current % grid.Width == 0)
                        {
                            if (current != starter) _destinations.Add(current);
                        }
                        else if (int_grid[current - 1] != 1) _destinations = investigatePaths(grid, int_grid, _destinations, starter, current - 1);
                    }
                }
            }
            return _destinations;
        }

        public void run()
        {
            running = true;
            releaseRunning = true;

            animationThread.Start();
            carsThread.Start();

            int i = 0;
            foreach (Thread thread in inFlowFlagThreads)
            {
                thread.Start(i);
                i++;
            }
        }

        public void pause()
        {
            paused = true;
        }

        public void stop()
        {
            running = false;
            releaseRunning = false;

            foreach (Thread thread in inFlowFlagThreads) thread.Abort();
            carsThread.Abort();
            animationThread.Abort();
        }

        private void releaseCars(object obj)
        {
            //Console.WriteLine("WCHODZE W FLAGI");
            int flag_index = (int)obj;
            int counter = 0;
            int interval = 0;
            int sleepTime = 0;
            while (releaseRunning)
            {
                if (!paused)
                {
                    KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>> inFlowFlag = new KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>();
                    lock (inFlowFlags)
                    {
                        inFlowFlag = inFlowFlags[flag_index];
                    }
                    if (interval >= inFlowFlag.Key.Intervals.Count) interval = 0;
                    if (counter < inFlowFlag.Key.Intervals[interval].Value)
                    {
                        //Console.WriteLine("chce sie wysrac na " + inFlowFlag.Value.Key + " " + inFlowFlag.Value.Value + " (Grid capacity: " + coreTraffic.getGrid().Tiles.Capacity + ")");
                        if (coreTraffic.getGrid().Tiles[inFlowFlag.Value.Key].Road.Lanes[inFlowFlag.Value.Value].Slots[0].Car_access == true)
                        {
                            lock (destinations)
                            {
                                int rand = 0;
                                //Console.WriteLine("flag_index = " + flag_index);
                                if (destinations[flag_index].Count > 1)
                                {
                                    Random random = new Random();
                                    rand = random.Next(0, destinations[flag_index].Count - 1);
                                }
                                List<int> tempList = destinations[flag_index];
                                lock (cars)
                                {
                                    Car car = new Car("car", inFlowFlag.Value.Key, inFlowFlag.Value.Value, tempList[rand], coreTraffic);
                                    addCar(car);
                                    car.animate();
                                    coreTraffic.getGrid().Tiles[inFlowFlag.Value.Key].Road.Lanes[inFlowFlag.Value.Value].Slots[0].Car_access = false;
                                    //Console.WriteLine("Wysrywam auto na " + inFlowFlag.Value.Key + " " + inFlowFlag.Value.Value + " z destinations[" + flag_index + "].Count = " + destinations[flag_index].Count);
                                    counter++;
                                }
                            }
                            try
                            {
                                sleepTime = inFlowFlag.Value.Value /60 * 60 * 1000;
                                //sleepTime = (int)((inFlowFlag.Key.Intervals[interval + 1].Key.Millisecond - inFlowFlag.Key.Intervals[interval].Key.Millisecond) / inFlowFlag.Key.Intervals[interval].Value);
                            }
                            catch (ArgumentOutOfRangeException)
                            {
                                //sleepTime = (int)((inFlowFlag.Key.Intervals[0].Key.Millisecond - inFlowFlag.Key.Intervals[interval].Key.Millisecond) / inFlowFlag.Key.Intervals[interval].Value);
                            }
                        }
                    }
                    else counter = 0;

                    Thread.Sleep(1000 / coreTraffic.getSpeed());
                }
            }
        }

        private void moveCars()
        {
            while (running) if (!paused)
                {
                    lock (cars)
                    {
                        for (int i = 0; i < cars.Count; i++)
                        {
                            //Console.Write("Car " + i + ": ");
                            bool move = cars[i].move();
                            if (!move)
                            {
                                //Console.WriteLine("Removed.");
                                removeCar(cars[i]);
                            }
                        }
                    }
                    Thread.Sleep(10 / coreTraffic.getSpeed());
                }
        }

        private void animateCars()
        {
            while (running)
            {
                lock (cars)
                {
                    for (int i = 0; i < cars.Count; i++)
                    {
                        //Console.Write("Car " + i + ": ");
                        cars[i].animate();
                    }
                    Thread.Sleep(20);
                    coreTraffic.drawAll();
                }
            }
        }

        #region Serialize
        public CarFlow(SerializationInfo info, StreamingContext ctxt)
        {
            cars = (List<Car>)info.GetValue("Cars", typeof(List<Car>));
          //  this.inFlowFlags = (List<KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>>)info.GetValue("InFlowFlags", typeof(List<KeyValuePair<Grid.InFlowFlag, KeyValuePair<int, int>>>));
            this.inFlowFlagThreads = (List<Thread>)info.GetValue("InFlowFlagThreads", typeof(List<Thread>));
            this.carsThread = (Thread)info.GetValue("CarsThread", typeof(Thread));
            this.animationThread = (Thread)info.GetValue("AnimationThread", typeof(Thread));
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Cars", cars);
          //  info.AddValue("InFlowFlags", this.inFlowFlags);
            info.AddValue("InFlowFlagThreads", this.inFlowFlagThreads);
            info.AddValue("CarsThread", this.carsThread);
            info.AddValue("AnimationThread", this.animationThread);
        }
        #endregion
    }
}
